import { type RefObject } from 'react'

/**
 * 处理列表视图基础逻辑
 * - 开启/关闭
 * - 搜索词
 * - 显示位置
 */
export interface ViewData {
  search: string // @ 搜索词
  node: Text // 通过哪个 text node 匹配到搜索词（提交 @ 内容时可能失去 selection 状态，但又需要知道此信息以替换文本框内容，所以要记录下来）
  offset: number // 匹配到搜索词时的光标位置
  position: { left: number; bottom: number } // 列表显示位置
}
export function useAtView(inputRef: RefObject<HTMLElement>) {
  const [view, setView] = useState<ViewData | null>(null) // 有值代表开启视图
  const close = useCallback(() => setView(null), [setView])

  // 基于文本框内容及光标位置，开启/关闭视图
  useEffect(() => {
    const input = inputRef.current
    if (!input) return

    const updateView = () => {
      const match = matchAtSearch()
      setView(match !== null ? { ...match, position: getCaretPosition() } : null)
    }

    // 光标位置变化时，更新 @ 状态（但在按 Backspace、Delete、Enter 时不会触发）
    const startHandleSelection = () => document.addEventListener('selectionchange', updateView)
    const stopHandleSelection = () => document.removeEventListener('selectionchange', updateView)
    input.addEventListener('focus', startHandleSelection)
    input.addEventListener('blur', stopHandleSelection)
    // 按下 Backspace、Delete 键时，更新 @ 状态
    const updateByKey = (e: KeyboardEvent) => {
      if (e.key === 'Backspace' || e.key === 'Delete') updateView()
    }
    input.addEventListener('keyup', updateByKey)
    return () => {
      input.removeEventListener('focus', startHandleSelection)
      input.removeEventListener('blur', stopHandleSelection)
      input.removeEventListener('keyup', updateByKey)
    }
  }, [inputRef])

  // 按下 ESC 时关闭视图
  useEffect(() => {
    if (!view) return
    const handleESC = (e: KeyboardEvent) => {
      if (e.key === 'Escape') {
        close()
      }
    }
    document.addEventListener('keydown', handleESC)
    return () => document.removeEventListener('keydown', handleESC)
  }, [view, close])

  // 鼠标点击视图外部时，关闭视图
  // 1. 点击事件要监听 mousedown。鼠标点击文本框使焦点从外部回到文本框时，事件顺序是 mousedown -> selectionchange -> click。
  //    如果监听 click，会导致 selectionchange 触发列表显示后又立刻被关闭，界面闪烁
  // 2. 视图渲染时，也要监听 mousedown 事件，禁止冒泡，以保证视图内的点击不会误触发此行为
  useEffect(() => {
    if (!view) return
    document.addEventListener('mousedown', close)
    return () => document.removeEventListener('mousedown', close)
  }, [view, inputRef, close])

  return {
    view,
    close, // 用于其他逻辑触发关闭
  }
}

/**
 * 判断文本框状态是否符合 @ 开启规则。
 * 若符合，返回匹配信息及 @ 搜索词；否则返回 null。
 * 注意：搜索词为空字符串也代表开启。
 */
function matchAtSearch() {
  const selection = document.getSelection()
  if (selection?.isCollapsed !== true || !(selection.anchorNode instanceof Text)) return null // 不是单独光标（有选中区域）或光标不处于纯文本中，不处理

  const [text, afterText] = [
    selection.anchorNode.data.slice(0, selection.anchorOffset),
    selection.anchorNode.data.slice(selection.anchorOffset),
  ]
  const searchMatch = /(?<=(^|\s)@)[^\s]*$/.exec(text)
  const afterMatch = !afterText || !!/\s/.exec(afterText[0]!)
  if (!searchMatch || !afterMatch) return null // 光标所处文字区域不匹配 @xxx 格式，不处理

  const search = searchMatch[0]!
  return {
    node: selection.anchorNode,
    offset: selection.anchorOffset,
    search,
  }
}

/**
 * 取得光标坐标
 */
function getCaretPosition() {
  const selection = document.getSelection()
  if (selection) {
    const range = selection.getRangeAt(0)
    const node = range.startContainer
    const offset = range.startOffset
    const pageOffset = { x: window.pageXOffset, y: window.pageYOffset }

    const r2 = document.createRange()
    r2.setStart(node, offset - 1)
    r2.setEnd(node, offset)
    const rect = r2.getBoundingClientRect()
    return {
      left: rect.right + pageOffset.x,
      bottom: document.body.offsetHeight - (rect.bottom + pageOffset.y),
    }
  } else {
    return { left: 100, bottom: 100 } // fallback
  }
}
